package com.kehutong.admin.controler;
 
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.kehutong.admin.entity.Menu;
import com.kehutong.admin.util.*;
import org.coraframework.authz.AccessToken;
import org.coraframework.authz.AuthenticationInfo;
import org.coraframework.authz.Subject;
import org.coraframework.authz.SubjectUtil;
import org.coraframework.authz.exception.AuthenticationException;
import org.coraframework.authz.exception.IncorrectCredentialsException;
import org.coraframework.authz.exception.NotPermissionException;
import org.coraframework.authz.exception.UnknownAccountException;
import org.coraframework.authz.exception.UnloginException;
import org.coraframework.boot.Application;
import org.coraframework.inject.Inject;
import org.coraframework.json.JSONObject;
import org.coraframework.logger.Logger;
import org.coraframework.logger.LoggerFactory;
import org.coraframework.mvc.ActionResult;
import org.coraframework.mvc.MvcAction;
import org.coraframework.mvc.http.ReqMapping;
import org.coraframework.mvc.http.result.Result;
import org.coraframework.orm.Pool;
import org.coraframework.orm.exception.ORMException;
import org.coraframework.orm.jdbc.JdbcSession;
import org.coraframework.orm.jdbc.connection.ThreadLocalCache;
import org.coraframework.util.Objects;
import org.coraframework.util.time.TimeUtil;

import com.kehutong.admin.entity.Corp;
import com.kehutong.admin.entity.User;
import com.kehutong.admin.service.UserHelper;
import com.kehutong.admin.util.service.IVerifyCodeGen;
import com.kehutong.admin.util.service.SimpleCharVerifyCodeGenImpl;
import com.kehutong.admin.util.service.UserService;
import com.kehutong.admin.util.service.VerifyCode;
import com.kehutong.common.DoveClient;
import com.kehutong.common.entity.Root;
import com.kehutong.common.enums.UserType;
import com.kehutong.common.util.Token;
 
@MvcAction
@ReqMapping("/admin")
public class LoginController {
 
    private static final Logger logger = LoggerFactory.getLogger(LoginController.class);
 
    private static final String RESET_PASSWORD = "RESET_PASSWD";
    private static final String RESET_PASSWORD_USER_ID = "RESET_PASSWD_USER_ID";
    private static final String RESET_PASSWORD_AUTH_CODE = "RESET_PASSWD_AUTH_CODE";

    @Inject
    private CacheUtil cacheUtil;
    @Inject
    private JdbcSession jdbcSession;
    @Inject
    private UserService userService;
    @Inject
    private DoveClient doveClient;
    @Inject
    private UserHelper userHelper;
    
    @ReqMapping("/setting")
    public Object setting(Token token) {
    	return getCorp(token);
    }
//    
//    @ReqMapping("/index")
//    public Object index(Token token) throws Exception {        
//    	Map<String, Object> result = new HashMap<>();
//    	User user = UserUtils.getUser();
//    	UserUtils.cleanLoginErrorCount(user.getLogin_name());
//    	
//    	result.put("systemTime", TimeUtil.timestamp().toString());
//    	result.put("userMenu", UserUtils.getMenuTree(true, false));
//    	result.put("userInfo", user);
//    	result.put("sysDict", userService.getDictMap());
//    	result.put("sysSetting", getCorp(token));
//    	if (user.isAdmin()) {
//    		result.put("permissions", Arrays.asList("*"));
//    	} else {
//    		result.put("permissions", UserUtils.getPermissions(true));
//    	}
//    	
//    	return Result.success(result);
//    }
    
    @ReqMapping("/index")
    public Object index(Token token, String menuId) throws Exception {
    	if (Objects.isNull(token)) {
    		return Result.unlogin();
    	}
        Map<String, Object> result = new HashMap<>();
        
        result.put("systemTime", TimeUtil.timestamp().toString());
        if(Objects.isEmpty(menuId)){
            result.put("userMenu", userHelper.menuTree(token.getRoles(), false));
        } else {
            Set<String> ids = userHelper.getMenuIds(token.getRoles(), true);
            List<Menu> menus = jdbcSession.findArray(Menu.class)
                    .markBegin()
                    .like("parent_ids", menuId).or()
                    .eq("id", menuId).or()
                    .eq("id", Root.ROOT_ID)
                    .markEnd().and()
                    .eq("deleted", false)
                    .eq("show", 1)
                    .in("id", ids)
                    .order("sort", "desc")
                    .exe();
            Menu root = TreeEntityUtil.organizeAsTree(menus);
            result.put("userMenu", root);
        }

        result.put("userInfo", token);
        result.put("sysDict", userHelper.getDictMap());
        result.put("sysSetting", getCorp(token));
        
        Set<String> pmsSet;
        if (Root.isRoot(token.getUuid())) {
        	pmsSet = new HashSet<>();
        	pmsSet.add("*");
        } else {
        	pmsSet = userHelper.getPermissions(Root.isRoot(token.getUuid()), new ArrayList<>(token.getRoles()), true).keySet();
        }

        result.put("permissions", pmsSet);
        token.setPermissions(pmsSet);
        //token.setDepartments(getDepartments(token));
        change(token.getSessionId(), pmsSet);
        
        return Result.success(result);
    }
    
    @ReqMapping("/isLogin")
    public ActionResult<String> isLogin(Token token, HttpServletRequest req, HttpServletResponse resp) throws IOException {
    	if (Objects.nonNull(token) && flushToken(req, resp)) {
    		return Result.success(TimeUtil.timeDate());
    	} else {
    		return Result.error(1, "未登录");
    	}
    }

    @ReqMapping("/getToken")
    public ActionResult<String> getToken(Token token, HttpServletRequest req, HttpServletResponse resp) throws IOException {
        if (Objects.nonNull(token) && flushToken(req, resp)) {
            return Result.success(token);
        } else {
            return Result.error(1, "未登录");
        }
    }
    
	// 账号密码登陆
    @ReqMapping("/login")
    public Object loginRest(Token session, HttpServletRequest req, HttpServletResponse resp, JSONObject jsonObject) {
		String username = jsonObject.getString("username");
		String password = jsonObject.getString("password"); 
//		String verifyCode = jsonObject.getString("verifyCode");
 
//        String token = "";
        try {
            Subject subject = SubjectUtil.getSubject();
//            AuthenticationInfo authenticationInfo = new AuthenticationInfo(username, password, verifyCode);
            AuthenticationInfo authenticationInfo = new AuthenticationInfo(username, password, null);
            AccessToken accessToken = subject.login(authenticationInfo);
            
            
            UserUtils.flushUserLoginTimeAndIp(req.getRemoteAddr());

            User user = jdbcSession.findById(User.class, accessToken.getUserId());
            Token token = login(Token.create()
            		.setUuid(user.getId())
            		.setSessionId(accessToken.getSessonId())
            		.setCompanyNo(user.getCompanyNo())
            		.setCompanyName(user.getCompanyName())
            		.setUserId(user.getLogin_name())
            		.setUsername(user.getName())
            		.setPermissions(UserUtils.getPermissions(user))
            		.setUserType(UserType.system))
                    .setUserCreateTime(user.getCreateTime().getTime());
            
            return token;
        } catch (NotPermissionException e) {
            logger.info("验证码验证失败");
            return Result.error(0002, "您输入的验证码不正确");
        } catch (UnknownAccountException e) {
//            UserUtils.increaseLoginErrorCount(username);
            logger.info("用户不存在", username);
            boolean isNeedValidCode = UserUtils.isNeedValidCode(username);
            return Result.error(0001, "您输入的帐号或密码有误", isNeedValidCode);
        } catch (IncorrectCredentialsException e) {
//        	UserUtils.increaseLoginErrorCount(username);
            logger.info("用户[{}]密码验证失败", username);
            return Result.error(0001, "您输入的帐号或密码有误");
        } catch (UnloginException e) {
            // 记录日志，有未处理的验证失败
            logger.error(e.getMessage(), e);
            return Result.unlogin();
        } catch (AuthenticationException e) {
            // 记录日志，有未处理的验证失败
            logger.error(e.getMessage(), e);
            return Result.error(0, e.getMessage());
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            return Result.error(0, e.getMessage());
        }
    }
 
    @ReqMapping("/logout")
    public ActionResult<String> logout(Token token, HttpServletRequest req, HttpServletResponse resp) throws Exception {
    	SubjectUtil.expiredSubject();
        CookieUtils.removeCookie(req, resp);
        if (Objects.nonNull(token)) {
        	logout(token.getSessionId());
        }
        
        return Result.success();
    }
 
    /**
     * 验证码
     *
     * @param req
     * @param resp
     */
    @ReqMapping("/verifyCode")
    public void verifyCode(HttpServletRequest req, HttpServletResponse resp, JSONObject jsonObject) {
        IVerifyCodeGen iVerifyCodeGen = new SimpleCharVerifyCodeGenImpl();
        try {
            VerifyCode verifyCode = iVerifyCodeGen.generate(80, 28);
 
            String cookieValue = CookieUtils.getCookieValueIfNullThenSetCookie(req, resp);
            cacheUtil.set(cookieValue + "_" + GlobalConfig.LOGIN, verifyCode.getCode(), GlobalConfig.getRegistryCodeExpireTime());
 
            resp.setHeader("Pragma", "no-cache");
            resp.setHeader("Cache-Control", "no-cache");
            resp.setDateHeader("Expires", 0);
            resp.setContentType("image/jpeg");
            resp.getOutputStream().write(verifyCode.getImgBytes());
            resp.getOutputStream().flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    
 
    @ReqMapping("/token")
    public Object token(Token token) {
        if (Objects.isNull(token)) {
            return Result.error(0, "获取Token失败");
        }
        
        return token;
    }
 
    public void validVerifyCode(HttpServletRequest request, String captcha) {
        String cookieValue = CookieUtils.getCookieValue(request);
        String validCode = cacheUtil.get(cookieValue + "_" + GlobalConfig.LOGIN);
        if (Objects.isEmpty(captcha) || !captcha.toUpperCase().equals(validCode)) {
            throw new OneBaseException("0001", "验证码错误.");
        }
        cacheUtil.remove(cookieValue + "_" + GlobalConfig.LOGIN);
    }
 
    @ReqMapping(value = "/validAccount")
    public ActionResult<String> validAccount(HttpServletRequest req, HttpServletResponse resp, JSONObject jsonObject) throws IOException {
    	String account = jsonObject.getString("account");
    	String captcha = jsonObject.getString("captcha");
        // 验证验证码是否正确
        validVerifyCode(req, captcha);
 
        // 验证系统中是否有该用户
        User user = jdbcSession.findOne(User.class).eq("login_name", account).exe();
        if (user == null) {
            return Result.error(0002, "用户不存在");
        }
        if (Objects.isEmpty(user.getEmail())) {
            return Result.error(0002, "用户未设置邮箱，请联系管理员重置密码");
        }
 
        // 生成验证码
        String validCode = RandomUtils.randomNumberString(6);
        logger.info("Send ValidCode: {}", validCode);
 
        // 获取Cookie并缓存验证码
        String cookieValue = CookieUtils.getCookieValueIfNullThenSetCookie(req, resp);
        cacheUtil.set(cookieValue + "_" + RESET_PASSWORD, validCode, GlobalConfig.getRegistryCodeExpireTime());
 
        // 存储USER＿ID以便第三步重置密码的时候使用（半个小时有效期）
        cacheUtil.set(cookieValue + "_" + RESET_PASSWORD_USER_ID, user.getId(), GlobalConfig.getRegistryCodeExpireTime());
 
// 		     发送邮件通知用户
//        Map<String, Object> mailMap = new HashMap<>();
//        mailMap.put("registryCode", validCode);
//        mailMap.put("expireTime", String.valueOf(GlobalConfig.getRegistryCodeExpireTime() / 60));
//        mailMap.put("companyName", GlobalConfig.getCompanyName());
//        iMessageSenderService.sendEmail(userInfoEO.getUserContactInfo().getEmail(), "重置密码", baseTemplateEOService.fillTemplateByCode("003", placeholderMap));
 
        return Result.success(user.getEmail());
    }
 
    @ReqMapping(value = "/validVerifyCode")
    public ActionResult<String> validVerifyCode(HttpServletRequest req, JSONObject jsonObject) throws IOException {
    	String verifyCode = jsonObject.getString("verifyCode");
    	// 验证
        if (Objects.isEmpty(verifyCode)) {
            return Result.error(0003, "请输入验证码");
        }
        String cookieValue = CookieUtils.getCookieValue(req);
        String validCodeRedis = cacheUtil.get(cookieValue + "_" + RESET_PASSWORD);
        if (Objects.isEmpty(validCodeRedis)) {
            return Result.error(0003, "验证码已经失效");
        }
        if (!validCodeRedis.equals(verifyCode)) {
            return Result.error(0003, "请输入正确的验证码");
        }
 
        // 验证通过清除验证码
        cacheUtil.remove(cookieValue + "_" + RESET_PASSWORD);
 
        // 设置标识表示验证通过了（没什么意义，就是一个标识）
        cacheUtil.set(cookieValue + "_" + RESET_PASSWORD, UUID.randomUUID(), GlobalConfig.getRegistryCodeExpireTime());
        return Result.success();
    }
 
    @ReqMapping(value = "/resetPassword")
    public ActionResult<String> resetPassword(HttpServletRequest req, JSONObject jsonObject) throws IOException {
    	String password = jsonObject.getString("password");
        if (Objects.isEmpty(password)) {
            return Result.error(0001, "密码不能为空");
        }
 
        String cookieValue = CookieUtils.getCookieValue(req);
        String authCode = cacheUtil.get(cookieValue + "_" + RESET_PASSWORD_AUTH_CODE);
        String userId = cacheUtil.get(cookieValue + "_" + RESET_PASSWORD_USER_ID);
        if (Objects.isEmpty(authCode) || Objects.isEmpty(userId)) {
            return Result.error(0001, "重置密码失败，请重试");
        }
 
        User user = Pool.get(User.class, userId);
		user.setPassword(Subject.encrypt(password));
		
		jdbcSession.updateById(user);
 
        // 修改成功清除缓存
        cacheUtil.remove(cookieValue + "_" + RESET_PASSWORD_AUTH_CODE, cookieValue + "_" + RESET_PASSWORD_USER_ID);
        return Result.success();
    }
    
    
    private boolean flushToken(HttpServletRequest req, HttpServletResponse resp) throws IOException {
  		AccessToken token = SubjectUtil.getSubject().getToken();
  		if (token == null) {
  			return false;
  		}
  		
  		if (TimeUtil.timeMillis() < token.getFlushTime().getTime()) {
  			token.flushTime();
  			CookieUtils.setCookie(req, resp, token.getSessonId());
  			return true;
  		}
  		
  		return false;
  	}
    
    
    /**
	 * <p>用户登陆</p>
	 * @param token
	 * @return
	 * @throws Exception
	 */
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public Token login(Token token) throws Exception {
		JSONObject rs = doveClient.post("/kht-bin/session/service/login", http->{
			JSONObject params = token.toJSON();
			params.forEach((k, v)->{
				if (Objects.nonNull(v)) {
					if (v instanceof List) {
						http.addParam(k, String.join(",", (List)v));
					} else {
						http.addParam(k, v.toString());
					}
				}
			});
    	});
		
		if (rs.getInteger("code") == 200) {
			return token;
		} else {
			throw new ORMException("login fail: " + rs.getString("message"));
		}
	}
	
	/**
	 * <p>用户登出</p>
	 * @param id
	 * @throws Exception
	 */
	public void logout(String id) throws Exception {
		doveClient.post("/kht-bin/session/service/logout", http->{
			http.addParam("sessionId", id);
    	});
	}
	
	/**
	 * <p>用户登出</p>
	 * @param id
	 * @throws Exception
	 */
	public void change(String id, Set<String> pemissions) throws Exception {
		doveClient.post("/kht-bin/session/service/change", http->{
			http.addParam("id", id);
			http.addParam("permissions", String.join(",", pemissions));
    	});
	}
	
	public Object getCorp(Token token) {
		Corp corp;
		if (Objects.isNull(token)) {
			corp = jdbcSession.findById(Corp.class, Root.ROOT_ID);
			return toJSON(corp);
		}
		
		corp = jdbcSession.findById(Corp.class, token.getCompanyNo());
		if (Objects.nonNull(corp) && Objects.nonEmpty(corp.getLogo())) {
			return toJSON(corp);
		}
		
		corp = jdbcSession.findById(Corp.class, Root.ROOT_ID);
		return toJSON(corp);
	}
	
	private JSONObject toJSON(Corp corp) {
		JSONObject jsonObject = new JSONObject();
		
		jsonObject.put("name", corp.getName());
		jsonObject.put("fullName", corp.getFullName());
		jsonObject.put("logo", corp.getLogo());
		jsonObject.put("url", corp.getUrl());
		jsonObject.put("version", "v1.0.0");
		jsonObject.put("copyright", "@2020-2023");
		jsonObject.put("corpId", Application.getConfig("web").getString("corpid"));
		
		return jsonObject;
	}
	
	private List<String> getDepartments(Token token) {
	   if (Objects.isEmpty(token.getCompanyNo())) {
		   return Collections.emptyList();
       }
	   ThreadLocalCache.current().setCorpId(token.getCompanyNo());
	   // TODO 获取员工所有组织架构
//	   Employee employee = Pool.get(Employee.class, user.getId());
//	   if (Objects.isNull(employee)) {
//       }
//	   return employee.getOfficeList();
	   return Collections.emptyList();
	}
}